package org.spiderflow.core.expression.parsing.ast.operation;

import org.spiderflow.core.expression.ExpressionError;
import org.spiderflow.core.expression.ExpressionTemplate;
import org.spiderflow.core.expression.ExpressionTemplateContext;
import org.spiderflow.core.expression.parsing.ast.Expression;
import org.spiderflow.core.expression.parsing.ast.access.VariableAccess;
import org.spiderflow.core.expression.parsing.enums.BinaryOperator;
import org.spiderflow.core.expression.parsing.token.Token;

import java.io.IOException;
import java.util.Objects;

/**
 * A binary operation represents arithmetic operators, like addition or division, comparison operators, like less than or
 * equals, logical operators, like and, or an assignment.
 *
 * @author zhaoshihao
 * @version 2021/11/9 14:34
 */
public class BinaryOperation extends Expression {
    private static final String RIGHT_ERROR_LOG = "右操作值必须是Boolean，实际为 %s。";
    private static final String LEFT_ERROR_LOG = "左操作值必须是Boolean，实际为 %s。";
    private final Expression leftOperand;
    private final BinaryOperator operator;
    private final Expression rightOperand;

    public BinaryOperation(Expression leftOperand, Token operator, Expression rightOperand) {
        super(operator.getSpan());
        this.leftOperand = leftOperand;
        this.operator = BinaryOperator.getOperator(operator);
        this.rightOperand = rightOperand;
    }

    public Expression getLeftOperand() {
        return leftOperand;
    }

    public BinaryOperator getOperator() {
        return operator;
    }

    public Expression getRightOperand() {
        return rightOperand;
    }

    private Object evaluateAddition(Object left, Object right) {
        if (left instanceof String || right instanceof String) {
            return left.toString() + right.toString();
        }
        if (left instanceof Double || right instanceof Double) {
            return ((Number) left).doubleValue() + ((Number) right).doubleValue();
        }
        if (left instanceof Float || right instanceof Float) {
            return ((Number) left).floatValue() + ((Number) right).floatValue();
        }
        if (left instanceof Long || right instanceof Long) {
            return ((Number) left).longValue() + ((Number) right).longValue();
        }
        if (left instanceof Integer || right instanceof Integer) {
            return ((Number) left).intValue() + ((Number) right).intValue();
        }
        if (left instanceof Short || right instanceof Short) {
            return ((Number) left).shortValue() + ((Number) right).shortValue();
        }
        if (left instanceof Byte || right instanceof Byte) {
            return ((Number) left).byteValue() + ((Number) right).byteValue();
        }

        throw ExpressionError.error("Operands for addition operator must be numbers or strings, got " + left + ", " + right + ".", getSpan());
    }

    private Object evaluateSubtraction(Object left, Object right) {
        if (left instanceof Double || right instanceof Double) {
            return ((Number) left).doubleValue() - ((Number) right).doubleValue();
        } else if (left instanceof Float || right instanceof Float) {
            return ((Number) left).floatValue() - ((Number) right).floatValue();
        } else if (left instanceof Long || right instanceof Long) {
            return ((Number) left).longValue() - ((Number) right).longValue();
        } else if (left instanceof Integer || right instanceof Integer) {
            return ((Number) left).intValue() - ((Number) right).intValue();
        } else if (left instanceof Short || right instanceof Short) {
            return ((Number) left).shortValue() - ((Number) right).shortValue();
        } else if (left instanceof Byte || right instanceof Byte) {
            return ((Number) left).byteValue() - ((Number) right).byteValue();
        } else {
            throw ExpressionError.error("Operands for subtraction operator must be numbers" + left + ", " + right + ".", getSpan());
        }
    }

    private Object evaluateMultiplication(Object left, Object right) {
        if (left instanceof Double || right instanceof Double) {
            return ((Number) left).doubleValue() * ((Number) right).doubleValue();
        } else if (left instanceof Float || right instanceof Float) {
            return ((Number) left).floatValue() * ((Number) right).floatValue();
        } else if (left instanceof Long || right instanceof Long) {
            return ((Number) left).longValue() * ((Number) right).longValue();
        } else if (left instanceof Integer || right instanceof Integer) {
            return ((Number) left).intValue() * ((Number) right).intValue();
        } else if (left instanceof Short || right instanceof Short) {
            return ((Number) left).shortValue() * ((Number) right).shortValue();
        } else if (left instanceof Byte || right instanceof Byte) {
            return ((Number) left).byteValue() * ((Number) right).byteValue();
        } else {
            throw ExpressionError.error("Operands for multiplication operator must be numbers" + left + ", " + right + ".", getSpan());
        }
    }

    private Object evaluateDivision(Object left, Object right) {
        if (left instanceof Double || right instanceof Double) {
            return ((Number) left).doubleValue() / ((Number) right).doubleValue();
        } else if (left instanceof Float || right instanceof Float) {
            return ((Number) left).floatValue() / ((Number) right).floatValue();
        } else if (left instanceof Long || right instanceof Long) {
            return ((Number) left).longValue() / ((Number) right).longValue();
        } else if (left instanceof Integer || right instanceof Integer) {
            return ((Number) left).intValue() / ((Number) right).intValue();
        } else if (left instanceof Short || right instanceof Short) {
            return ((Number) left).shortValue() / ((Number) right).shortValue();
        } else if (left instanceof Byte || right instanceof Byte) {
            return ((Number) left).byteValue() / ((Number) right).byteValue();
        } else {
            throw ExpressionError.error("Operands for division operator must be numbers" + left + ", " + right + ".", getSpan());
        }
    }

    private Object evaluateModulo(Object left, Object right) {
        if (left instanceof Double || right instanceof Double) {
            return ((Number) left).doubleValue() % ((Number) right).doubleValue();
        } else if (left instanceof Float || right instanceof Float) {
            return ((Number) left).floatValue() % ((Number) right).floatValue();
        } else if (left instanceof Long || right instanceof Long) {
            return ((Number) left).longValue() % ((Number) right).longValue();
        } else if (left instanceof Integer || right instanceof Integer) {
            return ((Number) left).intValue() % ((Number) right).intValue();
        } else if (left instanceof Short || right instanceof Short) {
            return ((Number) left).shortValue() % ((Number) right).shortValue();
        } else if (left instanceof Byte || right instanceof Byte) {
            return ((Number) left).byteValue() % ((Number) right).byteValue();
        } else {
            throw ExpressionError.error("Operands for modulo operator must be numbers" + left + ", " + right + ".", getSpan());
        }
    }

    private boolean evaluateLess(Object left, Object right) {
        if (left instanceof Double || right instanceof Double) {
            return ((Number) left).doubleValue() < ((Number) right).doubleValue();
        } else if (left instanceof Float || right instanceof Float) {
            return ((Number) left).floatValue() < ((Number) right).floatValue();
        } else if (left instanceof Long || right instanceof Long) {
            return ((Number) left).longValue() < ((Number) right).longValue();
        } else if (left instanceof Integer || right instanceof Integer) {
            return ((Number) left).intValue() < ((Number) right).intValue();
        } else if (left instanceof Short || right instanceof Short) {
            return ((Number) left).shortValue() < ((Number) right).shortValue();
        } else if (left instanceof Byte || right instanceof Byte) {
            return ((Number) left).byteValue() < ((Number) right).byteValue();
        } else {
            throw ExpressionError.error("Operands for less operator must be numbers" + left + ", " + right + ".", getSpan());
        }
    }

    private Object evaluateLessEqual(Object left, Object right) {
        if (left instanceof Double || right instanceof Double) {
            return ((Number) left).doubleValue() <= ((Number) right).doubleValue();
        } else if (left instanceof Float || right instanceof Float) {
            return ((Number) left).floatValue() <= ((Number) right).floatValue();
        } else if (left instanceof Long || right instanceof Long) {
            return ((Number) left).longValue() <= ((Number) right).longValue();
        } else if (left instanceof Integer || right instanceof Integer) {
            return ((Number) left).intValue() <= ((Number) right).intValue();
        } else if (left instanceof Short || right instanceof Short) {
            return ((Number) left).shortValue() <= ((Number) right).shortValue();
        } else if (left instanceof Byte || right instanceof Byte) {
            return ((Number) left).byteValue() <= ((Number) right).byteValue();
        } else {
            throw ExpressionError.error("Operands for less/equal operator must be numbers" + left + ", " + right + ".", getSpan());
        }
    }

    private Object evaluateGreater(Object left, Object right) {
        if (left instanceof Double || right instanceof Double) {
            return ((Number) left).doubleValue() > ((Number) right).doubleValue();
        } else if (left instanceof Float || right instanceof Float) {
            return ((Number) left).floatValue() > ((Number) right).floatValue();
        } else if (left instanceof Long || right instanceof Long) {
            return ((Number) left).longValue() > ((Number) right).longValue();
        } else if (left instanceof Integer || right instanceof Integer) {
            return ((Number) left).intValue() > ((Number) right).intValue();
        } else if (left instanceof Short || right instanceof Short) {
            return ((Number) left).shortValue() > ((Number) right).shortValue();
        } else if (left instanceof Byte || right instanceof Byte) {
            return ((Number) left).byteValue() > ((Number) right).byteValue();
        } else {
            throw ExpressionError.error("Operands for greater operator must be numbers" + left + ", " + right + ".", getSpan());
        }
    }

    private Object evaluateGreaterEqual(Object left, Object right) {
        if (left instanceof Double || right instanceof Double) {
            return ((Number) left).doubleValue() >= ((Number) right).doubleValue();
        } else if (left instanceof Float || right instanceof Float) {
            return ((Number) left).floatValue() >= ((Number) right).floatValue();
        } else if (left instanceof Long || right instanceof Long) {
            return ((Number) left).longValue() >= ((Number) right).longValue();
        } else if (left instanceof Integer || right instanceof Integer) {
            return ((Number) left).intValue() >= ((Number) right).intValue();
        } else if (left instanceof Short || right instanceof Short) {
            return ((Number) left).shortValue() >= ((Number) right).shortValue();
        } else if (left instanceof Byte || right instanceof Byte) {
            return ((Number) left).byteValue() >= ((Number) right).byteValue();
        } else {
            throw ExpressionError.error(String.format("比较更大或相等时必须是数字 left->{%s} right->{%s}。", left, right), getSpan());
        }
    }

    private Object evaluateAnd(Object left, ExpressionTemplate template, ExpressionTemplateContext context) throws IOException {
        if (!(left instanceof Boolean)) {
            throw ExpressionError.error(String.format(LEFT_ERROR_LOG, left), getLeftOperand().getSpan());
        }
        if (Boolean.FALSE.equals(left)) {
            return false;
        }
        Object right = getRightOperand().evaluate(template, context);
        if (!(right instanceof Boolean)) {
            throw ExpressionError.error(String.format(RIGHT_ERROR_LOG, right), getRightOperand().getSpan());
        }
        return Boolean.TRUE.equals(right);
    }

    private Object evaluateOr(Object left, ExpressionTemplate template, ExpressionTemplateContext context) throws IOException {
        if (!(left instanceof Boolean)) {
            throw ExpressionError.error(String.format(LEFT_ERROR_LOG, left), getLeftOperand().getSpan());
        }
        if (Boolean.TRUE.equals(left)) {
            return true;
        }
        Object right = getRightOperand().evaluate(template, context);
        if (!(right instanceof Boolean)) {
            throw ExpressionError.error(String.format(RIGHT_ERROR_LOG, right), getRightOperand().getSpan());
        }
        return Boolean.TRUE.equals(right);
    }

    private Object evaluateXor(Object left, Object right) {
        if (!(left instanceof Boolean)) {
            throw ExpressionError.error(String.format(LEFT_ERROR_LOG, left), getLeftOperand().getSpan());
        }
        if (!(right instanceof Boolean)) {
            throw ExpressionError.error(String.format(RIGHT_ERROR_LOG, right), getRightOperand().getSpan());
        }
        return (Boolean) left ^ (Boolean) right;
    }

    private Object evaluateEqual(Object left, Object right) {
        return Objects.equals(left, right);
    }

    private Object evaluateNotEqual(Object left, Object right) {
        return !Objects.equals(left, right);
    }

    @Override
    public Object evaluate(ExpressionTemplate template, ExpressionTemplateContext context) throws IOException {
        if (getOperator() == BinaryOperator.ASSIGNMENT) {
            if (!(getLeftOperand() instanceof VariableAccess)) {
                throw ExpressionError.error("Can only assign to top-level variables in context.", getLeftOperand().getSpan());
            }
            Object value = getRightOperand().evaluate(template, context);
            context.set(((VariableAccess) getLeftOperand()).getVariableName().getText(), value);
            return null;
        }

        Object left = getLeftOperand().evaluate(template, context);
        Object right = getOperator() == BinaryOperator.AND || getOperator() == BinaryOperator.OR ? null : getRightOperand().evaluate(template, context);

        switch (getOperator()) {
            case ADDITION:
                return evaluateAddition(left, right);
            case SUBTRACTION:
                return evaluateSubtraction(left, right);
            case MULTIPLICATION:
                return evaluateMultiplication(left, right);
            case DIVISION:
                return evaluateDivision(left, right);
            case MODULO:
                return evaluateModulo(left, right);
            case LESS:
                return evaluateLess(left, right);
            case LESS_EQUAL:
                return evaluateLessEqual(left, right);
            case GREATER:
                return evaluateGreater(left, right);
            case GREATER_EQUAL:
                return evaluateGreaterEqual(left, right);
            case EQUAL:
                return evaluateEqual(left, right);
            case NOT_EQUAL:
                return evaluateNotEqual(left, right);
            case AND:
                return evaluateAnd(left, template, context);
            case OR:
                return evaluateOr(left, template, context);
            case XOR:
                return evaluateXor(left, right);
            default:
                throw ExpressionError.error("Binary operator " + getOperator().name() + " not implemented", getSpan());
        }
    }
}
